/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.myfaces.extensions.cdi.forge.api.support;

import org.apache.myfaces.extensions.cdi.core.api.config.view.DefaultErrorView;
import org.apache.myfaces.extensions.cdi.core.api.security.Secured;
import org.apache.myfaces.extensions.cdi.jsf.api.config.view.Page;
import org.eclipse.jdt.core.dom.*;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * This class represents a TypeDeclaration resource holder and will manage the resources of a given ViewConfig.
 * From the ViewConfig perspective the Resources can be of two types, either pages or folders according to the
 * isInterface() flag.
 *
 * @author Cosmin Martinconi, 29.07.2011
 */
public class ViewConfigMetadata {

    private static AnnotationHelper annotationHelper = new AnnotationHelper();
    private static ImportHelper importHelper = new ImportHelper();

    private TypeDeclaration resource;
    private CompilationUnit origin;
    private File associatedFile;

    /**
     * Constructor.
     *
     * @param resource     The specific type declaration of the resource
     * @param origin       The origin ViewConfig compilation unit
     * @param parentFile   The parent file from which the associatedFile will be created
     */
    public ViewConfigMetadata(TypeDeclaration resource, CompilationUnit origin, File parentFile) {
        this.resource = resource;
        this.origin = origin;
        this.associatedFile = new File(parentFile, getFileName());
    }

    /**
     * Adds the newMetadata as a child to the current metadata creating an inner class/interface
     * that models the page/folder of the ViewConfig.
     *
     * @param newMetadata  The metadata instance that will be added as a child for this ViewConfigMetadata
     */
    public void addMetadata(ViewConfigMetadata newMetadata) {
        resource.bodyDeclarations().add(newMetadata.resource);
    }

    /**
     * Creates a new ViewConfigMetadata instance that will represent a page for the ViewConfig.
     * The new page will be created as a child of this metadata.
     *
     * @param pageName     The Page metadata name
     * @param isErrorPage  A flag that will enable the creation of an Error specific page
     * @return             The new created ViewConfigMetadata
     */
    public ViewConfigMetadata getNewPage(String pageName, boolean isErrorPage) {
        if (!isFolder()) {
            throw new ViewConfigException("Can not add create page for a page type!");
        }
        TypeDeclaration newPage = resource.getAST().newTypeDeclaration();
        newPage.setName(newPage.getAST().newSimpleName(pageName));
        newPage.setInterface(false);

        ViewConfigMetadata vcMetadata = new ViewConfigMetadata(newPage, origin, this.getAssociatedFile());
        annotationHelper.addAnnotation(vcMetadata, Page.class);
        newPage.modifiers().add(newPage.getAST().newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));

        if (isErrorPage) {
            SimpleType defaultErrorView  = newPage.getAST().newSimpleType(newPage.getAST().newSimpleName(DefaultErrorView.class.getSimpleName()));
            newPage.setStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY, defaultErrorView);
            importHelper.addImport(vcMetadata, DefaultErrorView.class);
        } else {
            newPage.superInterfaceTypes().add(resource.getAST().newSimpleType(newPage.getAST().newSimpleName(resource.getName().getIdentifier())));
            newPage.modifiers().add(newPage.getAST().newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD));
        }


        return vcMetadata;
    }

    /**
     * Creates a new ViewConfigMetadata instance that will represent a folder for the ViewConfig.
     * The new folder will be created as a child of this metadata.
     *
     * @param folderName  The Folder metadata name
     * @return            The new created ViewConfigMetadata
     */
    public ViewConfigMetadata getNewFolder(String folderName) {
        if (!isFolder()) {
            throw new ViewConfigException("Can not add folder for a page type!");
        }

        TypeDeclaration newFolder = resource.getAST().newTypeDeclaration();
        newFolder.setName(newFolder.getAST().newSimpleName(folderName));
        newFolder.setInterface(true);
        newFolder.superInterfaceTypes().add(resource.getAST().newSimpleType(newFolder.getAST().newSimpleName(resource.getName().getIdentifier())));
        newFolder.modifiers().add(newFolder.getAST().newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD));

        return new ViewConfigMetadata(newFolder, origin, this.getAssociatedFile());
    }

    /**
     * Adds a new interface as a super interface for this metadata.
     *
     * @param configMetadata  The ViewConfigMetadata that will be extended by this metadata
     * @return                Current ViewConfigMetadata instance
     */
    public ViewConfigMetadata addExtensionInterface(ViewConfigMetadata configMetadata) {
        resource.superInterfaceTypes().add(resource.getAST().newSimpleType(resource.getAST().newSimpleName(configMetadata.getName())));
        importHelper.addImport(this, configMetadata.toString());
        return this;
    }

    /**
     * Adds a new interface as a super interface for this metadata.
     *
     * @param extension   The class of the interface to extend
     * @return            Current ViewConfigMetadata instance
     */
    public ViewConfigMetadata addExtensionInterface(Class extension) {
        resource.superInterfaceTypes().add(resource.getAST().newSimpleType(resource.getAST().newSimpleName(extension.getSimpleName())));
        importHelper.addImport(this, extension);
        return this;
    }

    /**
     * Creates a List of Strings for the extended classes of the current metadata
     *
     * @return   The interfaces and Classes extended by this metadata
     */
    public List<String> getExtensionClasses() {
        List<String> extensionClasses = new ArrayList<String>();
        for(Object object : resource.superInterfaceTypes()) {
            extensionClasses.add(((SimpleType)object).getName().getFullyQualifiedName());
        }
        if (resource.getSuperclassType() != null) {
            extensionClasses.add(((SimpleType)resource.getSuperclassType()).getName().getFullyQualifiedName());
        }
        return extensionClasses;
    }

    /**
     * Configures the navigation type of the current metadata as REDIRECT.
     *
     * @return   Current ViewConfigMetadata instance
     */
    public ViewConfigMetadata setRedirectNavigation() {
        importHelper.addStaticImport(this, Page.NavigationMode.REDIRECT.getClass().getCanonicalName() + "." + Page.NavigationMode.REDIRECT.name());
        annotationHelper.addAnnotation(this, Page.class, "navigation", "REDIRECT");
        return this;
    }

    /**
     * Verifies if the navigation type of the metadata is REDIRECT
     *
     * @return  true if the navigation type is configured as REDIRECT,
     *          false otherwise
     */
    public boolean isRedirectNavigation() {
        return annotationHelper.hasNormalAnnotation(this, Page.class, "navigation", "REDIRECT");
    }

    /**
     *
     * @param voterName
     * @param voterQualifiedName
     * @return
     */
    public ViewConfigMetadata setSecured(String voterName, String voterQualifiedName) {
        TypeLiteral tl = resource.getAST().newTypeLiteral();
        tl.setType(resource.getAST().newSimpleType(resource.getAST().newName(voterName)));

        MemberValuePair mvp = resource.getAST().newMemberValuePair();
        mvp.setName(resource.getAST().newSimpleName("value"));
        mvp.setValue(tl);

        annotationHelper.addAnnotation(this, Secured.class, mvp);
        importHelper.addImport(this, voterQualifiedName);
        return this;
    }

    /**
     * Verifies if the current metadata is annotated with the {@code org.apache.myfaces.extensions.cdi.core.api.security.Secured} annotation
     *
     * @return  true if the annotation is found,
     *          false otherwise
     */
    public boolean isSecured() {
        return annotationHelper.hasAnnotation(this, Secured.class);
    }

    /**
     * Provides all ViewConfigMetadata child instances of the current metadata
     *
     * @return   child metadata list of this metadata
     */
    public List<ViewConfigMetadata> getMetadataChildList() {
        List<ViewConfigMetadata> childList = new ArrayList<ViewConfigMetadata>();
        for (TypeDeclaration td : resource.getTypes()) {
            childList.add(new ViewConfigMetadata(td, origin, this.getAssociatedFile()));
        }

        return childList;
    }

    /**
     * @return   The origin CompilationUnit of the ViewConfig.
     */
    public CompilationUnit getOrigin() {
        return origin;
    }

    /**
     * @return   The internal TypeDeclaration metadata for this metadata
     */
    public TypeDeclaration getInternal() {
        return resource;
    }

    /**
     * @return   The associated web file/folder for this metadata
     */
    public File getAssociatedFile() {
        return associatedFile;
    }

    /**
     * @return   The simple name of the metadata
     */
    public String getName() {
        return resource.getName().getFullyQualifiedName();
    }

    /**
     * @return   The appropriate name for the file associated with this metadata
     */
    public String getFileName() {
        StringBuilder fileName = new StringBuilder();
        fileName.append(getName().substring(0,1).toLowerCase())
                .append(getName().substring(1));
        if (!isFolder()) {
            fileName.append(".xhtml");
        }
        return fileName.toString();
    }

    /**
     * @return  true if the metadata is a ViewConfig folder
     *          false if the metadata is a ViewConfig page
     */
    public boolean isFolder() {
        return resource.isInterface();
    }

    @Override
    public String toString() {
        return origin.getPackage().getName().getFullyQualifiedName() + "." + resource.getName().getFullyQualifiedName();
    }

}
